package com.ahs.aspect;

import com.ahs.annotation.Lock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author riven zhang
 * @since 2021/5/25 11:45
 */
@Aspect
@Component
@Slf4j
public class DistributedLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private Timer timer = new Timer();

    @Pointcut("@annotation(com.ahs.annotation.Lock)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object lock(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String key = method.getAnnotation(Lock.class).value();
        String lock = UUID.randomUUID().toString();
        // 自旋，一直获取锁
        tryLock(key, lock, 3000L);
        try {
            log.info("获取分布式锁成功,当前线程：{}", Thread.currentThread());
            return point.proceed(point.getArgs());
        } finally {
            unlock(key);
        }
    }

    private boolean acquireLock(String key, String lock) {
        return redisTemplate.opsForValue().setIfAbsent(key, lock, 200L, TimeUnit.MILLISECONDS);
    }

    private void tryLock(String key, String lock) {
        while (!acquireLock(key, lock)) {
        }
    }

    private void tryLock(String key, String lock, long timeout) {
        long begin = System.currentTimeMillis();
        while (!acquireLock(key, lock)) {
            long end = System.currentTimeMillis();
            if ((end - begin) >= timeout) {
                log.info("获取分布式锁超时：{}", timeout);
                throw new RuntimeException("获取分布式锁超时");
            }
        }
    }

    private void watchDog(String key, String lock) {
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                renewal(key,lock);
            }
        }, 100L, 100L);
    }

    private void renewal(String key, String lock) {
        if (lock.equals(redisTemplate.opsForValue().get(key))) {
            redisTemplate.expire(key, 200L, TimeUnit.MILLISECONDS);
        }
    }

    private void unlock(String key) {
        timer.cancel();
        redisTemplate.delete(key);
    }

}
